<!--
 * @Author: 王坤
 * @LastEditors: 王坤
 * @Description: 图片列表
-->
<template>
  <div class="lc-image-list" ref="lcImageListRef" @scroll="handleScroll">
    <div v-if="imageList.length" class="lc-image-list__names" :style="namesStyle">
      <div v-for="(item, index) in imageList" :key="index" class="lc-image-list__names-item">
        {{ item[nameKey] }}
      </div>
    </div>
    <div class="lc-image-list__images">
      <div
        v-for="(column, colIndex) in reColumns"
        :key="column"
        class="lc-image-list__images-column"
        :style="imagesStyle"
        ref="imageListRefs"
      >
        <div
          v-for="(item, index) in getImageList(colIndex)"
          :key="index"
          class="lc-image-list__images-item"
          :style="imagesItemStyle"
        >
          <el-image class="lc-image-list__images-item-img" :src="item">
            <template v-slot:error>
              <div class="image-slot">
                <el-image class="lc-image-list__images-item-img" :src="defaultImage" />
              </div>
            </template>
          </el-image>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ElMessage } from 'element-plus';
import { onMounted, ref, computed } from 'vue';
import defaultImage from '@/assets/images/default-image.png';
import { request } from '@/apis/request';
const props = defineProps({
  columns: {
    type: [Number, String],
    default: 2
  },
  imageWidth: {
    type: String,
    default: '200px'
  },
  imageInterval: {
    type: String,
    default: '5px'
  },
  imageKey: {
    type: String,
    default: 'image'
  },
  nameKey: {
    type: String,
    default: 'name'
  },
  // 图片列表请求url
  action: {
    type: String,
    default: ''
  },
  method: {
    type: String,
    default: 'get'
  },
  // 请求头部
  headers: {
    type: Object,
    default: () => ({})
  },
  // 额外参数
  data: {
    type: Object,
    default: () => ({})
  },
  // 设置请求的返回数据类型
  responseType: {
    type: String,
    default: 'json'
  },
  responseDataName: {
    type: String,
    default: 'data'
  }
});
const lcImageListRef = ref();
const imageListRefs = ref([]);
const defaultImageInfo = ref({});
const imageList = ref([]);
const waterfallData = ref({
  imageList0: [],
  imageListHeight0: 0
});
// 当前已加载图片数量
const currentLoadedImgNumber = ref(0);
const isLoadImage = ref(false);
const canvas = document.createElement('canvas');
// 后续每次加载图片数量
const nextLoadImgNumber = 10;

/** ========== computed start ========== */
const reColumns = computed(() => {
  return props.columns ? parseInt(props.columns) : 2;
});
const namesStyle = computed(() => {
  return `
        margin-top: ${props.imageInterval};
        `;
});
const imagesStyle = computed(() => {
  const imgWidthNumber = parseInt(props.imageWidth);
  const defaultStyle = `margin-left: ${props.imageInterval};`;
  if (!imgWidthNumber) {
    return `
          ${defaultStyle}
          width: calc(100% / ${reColumns.value});
        `;
  }
  return defaultStyle;
});
const imagesItemStyle = computed(() => {
  const imgWidthNumber = parseInt(props.imageWidth);
  const imageWidth = imgWidthNumber ? props.imageWidth : `100%`;
  return `
        width: ${imageWidth};
        margin-top: ${props.imageInterval};
      `;
});
/** ========== computed end ========== */

/** ========== methods start ========== */
function initData() {
  for (let i = 0; i < reColumns.value; i++) {
    waterfallData.value[`imageList${i}`] = [];
    waterfallData.value[`imageListHeight${i}`] = 0;
  }
}
function handleScroll() {
  const scrollHeight = lcImageListRef.value.scrollHeight;
  const clientHeight = lcImageListRef.value.clientHeight;
  const scrollTop = lcImageListRef.value.scrollTop;
  if (clientHeight + scrollTop > scrollHeight - 10) {
    buildImageListNext();
  }
}
function getDefaultImageInfo() {
  const img = new Image();
  img.src = defaultImage;
  img.onload = () => {
    defaultImageInfo.value = img;
  };
}
function queryImageList() {
  if (!props.action) {
    ElMessage.info({ message: '图片列表暂无可用请求地址' });
    return;
  }
  request({
    url: props.action,
    method: props.method,
    headers: props.headers,
    params: props.data,
    data: props.data,
    responseType: props.responseType
  })
    .then((res) => {
      const newImageList = res[props.responseDataName];
      imageList.value.push(...newImageList);
      buildImageListFirst();
    })
    .catch(() => {});
}
function getImageList(index) {
  return waterfallData.value[`imageList${index}`];
}
async function buildImageListFirst() {
  isLoadImage.value = true;
  await buildImageWaterfall(0, () => {
    const height = lcImageListRef.value.clientHeight;
    let isBreak = true;
    for (let index = 0; index < reColumns.value; index++) {
      if (waterfallData.value[`imageListHeight${index}`] <= height) {
        isBreak = false;
        break;
      }
    }
    return isBreak;
  });
  isLoadImage.value = false;
}
async function buildImageListNext() {
  if (isLoadImage.value || currentLoadedImgNumber.value >= imageList.value.length) {
    return;
  }
  isLoadImage.value = true;
  const startIndex = currentLoadedImgNumber.value;
  const loadNumber = nextLoadImgNumber * reColumns.value;
  await buildImageWaterfall(startIndex, () => {
    if (currentLoadedImgNumber.value - startIndex >= loadNumber) return true;
    return false;
  });
  isLoadImage.value = false;
}
/**
 * 创建图片瀑布流
 * @param startIndex 从imageList某条数据开始循环
 * @param breakFn 退出条件
 */
async function buildImageWaterfall(startIndex, breakFn) {
  while (startIndex < imageList.value.length) {
    const image = imageList.value[startIndex][props.imageKey];
    const index = findImageListMinHeightIndex();
    const imgData = await imageRender(index, image);
    waterfallData.value[`imageListHeight${index}`] +=
      imgData.height + parseInt(props.imageInterval);
    waterfallData.value[`imageList${index}`].push(imgData.src);
    startIndex++;
    currentLoadedImgNumber.value++;
    if (breakFn()) return;
  }
}
function findImageListMinHeightIndex() {
  let minIndex = 0;
  for (let i = 1; i < reColumns.value; i++) {
    if (
      waterfallData.value[`imageListHeight${i}`] < waterfallData.value[`imageListHeight${minIndex}`]
    ) {
      minIndex = i;
    }
  }
  return minIndex;
}
function imageRender(index, image) {
  return new Promise((resolve) => {
    const img = new Image();
    img.src = image;
    img.crossOrigin = 'Annoymous';
    img.onload = () => {
      let imgWidth = parseInt(props.imageWidth);
      if (!imgWidth) {
        imgWidth = imageListRefs.value[0].offsetWidth;
      }
      resolve({
        src: getImageBase64(img),
        height: (imgWidth * img.height) / img.width
      });
    };
    img.onerror = () => {
      let imgWidth = parseInt(props.imageWidth);
      if (!imgWidth) {
        imgWidth = imageListRefs.value[0].offsetWidth;
      }
      resolve({
        src: defaultImage,
        height: (imgWidth * defaultImageInfo.value.height) / defaultImageInfo.value.width
      });
    };
  });
}
function getImageBase64(img) {
  const ctx = canvas.getContext('2d');
  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage(img, 0, 0);
  return canvas.toDataURL('image/jpg');
}
/** ========== methods end ========== */

onMounted(() => {
  queryImageList();
});

initData();
getDefaultImageInfo();
</script>

<style lang="scss">
.lc-image-list {
  display: flex;
  width: 100%;
  height: 100%;
  overflow: auto;

  &__names {
    width: 80px;
    border: 1px solid #ccc;
    padding: 10px;
    color: #222;

    &-item {
      width: 80px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }

  &__images {
    display: flex;
    flex-wrap: nowrap;
    flex: 1;

    &-item {
      flex-shrink: 0;

      &-img {
        display: block;
        width: 100%;
        height: 100%;
        color: #999;
      }
    }
  }
}
</style>
